using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json;
using NExt.Core.Model.Base;
using NExt.Serialization.Core;

namespace NExt.Serialization.JsonNet.Conversion
{
    public class ComponentConvertor : AbstractJsonConvertor<Component>
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Type runtimeType = value.GetType();

            writer.WriteStartObject();
            WriterFields(writer, value, runtimeType, serializer);
            WriterProperties(writer, value, runtimeType, serializer);
            WriterExtendedProperty(writer, value, serializer);
            writer.WriteEndObject();
        }

        private void WriterFields(JsonWriter writer, object value, Type runtimeType, JsonSerializer serializer)
        {
            IEnumerable<FieldInfo> fieldInfos = runtimeType.GetFields(BindingFlags.Public | BindingFlags.Instance);
            IList<KeyValuePair<string, string>> variables = new List<KeyValuePair<string, string>>();
            foreach (FieldInfo fieldInfo in fieldInfos)
            {
                string fieldName = fieldInfo.Name;
                object fieldValue = fieldInfo.GetValue(value);
                Component component = fieldValue as Component;
                if (component == null)
                {
                    continue;
                }

                string generatedId = IdGenerator.GetId();
                component.varId = generatedId;
                variables.Add(new KeyValuePair<string, string>(fieldName, generatedId));
            }

            bool isSerializableValue = IsSerializableValue(variables);
            if (!isSerializableValue)
            {
                return;
            }

            writer.WritePropertyName("vars");
            string serializedValue = this.Serialize(serializer, variables);
            writer.WriteRawValue(serializedValue);
        }

        private void WriterProperties(JsonWriter writer, object value, Type runtimeType, JsonSerializer serializer)
        {
            PropertyInfo[] propertyInfos = runtimeType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                MethodInfo methodInfo = propertyInfo.GetGetMethod();
                string propertyName = propertyInfo.Name;
                if (propertyName == "extended")
                {
                    continue;
                }

                WriteProperty(writer, value, propertyName, methodInfo, serializer);
            }
        }

        private void WriteProperty(JsonWriter writer, object instance, string propertyName, MethodInfo methodInfo, JsonSerializer serializer)
        {
            object propertyValue = methodInfo.Invoke(instance, null);
            bool isValueValid = IsSerializableValue(propertyValue);
            if (!isValueValid)
            {
                return;
            }

            string serializedValue = this.Serialize(serializer, propertyValue);
            bool isSerializedValueValid = IsSerializedValueValid(propertyName, serializedValue);
            if (!isSerializedValueValid)
            {
                return;
            }

            writer.WritePropertyName(propertyName);
            writer.WriteRawValue(serializedValue);
        }

        private void WriterExtendedProperty(JsonWriter writer, object instance, JsonSerializer serializer)
        {
            Component component = (Component)instance;
            object extended = component.extended;
            if (!IsSerializableValue(extended))
            {
                return;
            }

            Type extendedType = extended.GetType();
            IEnumerable<PropertyInfo> infos = GetPropertyInfos(extendedType);
            foreach (PropertyInfo info in infos)
            {
                MethodInfo methodInfo = info.GetGetMethod();
                string propertyName = info.Name;
                WriteProperty(writer, extended, propertyName, methodInfo, serializer);
            }
        }

        private static IEnumerable<PropertyInfo> GetPropertyInfos(Type objectType)
        {
            return objectType.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
        }

        private static bool IsSerializableValue(object value)
        {
            if (value == null)
            {
                return false;
            }

            if (value is string && string.IsNullOrEmpty(value as string))
            {
                return false;
            }

            if (value is IList && ((IList)value).Count == 0)
            {
                return false;
            }

            return true;
        }

        private static bool IsSerializedValueValid(string propertyName, string serializedValue)
        {
            if (propertyName == "listeners" && serializedValue == "{}")
            {
                return false;
            }

            if (serializedValue == "[]")
            {
                return false;
            }

            return true;
        }
    }
}